package asteroid

import (
	"fmt"
	"sync"
	"time"

	"gddgame.cc/galaxy/utils/def"
	"github.com/antlabs/timer"
)

type CallbackHandler func(success bool, data map[string]interface{})
type ToCallbackHandler func(success bool, data interface{})

type responseAction interface {
	response(msg *Msg, data interface{})
}

type responseManager struct {
	id      def.ID
	timeout time.Duration
	rmu     sync.RWMutex
	rid     int
	rr      map[int]*response
	timer   timer.Timer
}

func newResponseManager(id def.ID, timer timer.Timer) *responseManager {
	return &responseManager{
		id:      id,
		rid:     0,
		rmu:     sync.RWMutex{},
		rr:      make(map[int]*response, 30),
		timeout: time.Duration(2 * time.Second),
		timer:   timer,
	}
}

func (rm *responseManager) BindTimer(timer timer.Timer) {
	rm.timer = timer
}

func (rm *responseManager) clean() {
	rm.rid = 0
}
func (rm *responseManager) SetTimeout(ms time.Duration) {
	rm.timeout = time.Duration(time.Millisecond * ms)
}
func (rm *responseManager) onResponse(msg *Msg) {
	//fmt.Println("accept room rid:", msg.Rid)
	rm.rmu.RLock()
	response, ok := rm.rr[msg.Rid]
	rm.rmu.RUnlock()
	if !ok {
		return
	}
	if response.isData {
		d, _ := msg.message.Payload()
		response.Response(msg.From, d)
	} else if msg.message.IsNil() {
		response.Response(msg.From, nil)
	} else if response.getSerializer != nil {
		serializer := response.getSerializer()
		msg.message.UnSerialize(serializer)
		response.Response(msg.From, serializer)
	} else {
		response.Response(msg.From, msg.message.Data())
	}
}
func (rm *responseManager) addResponse(count int, response *response) int {
	rm.rmu.Lock()
	rm.rid++
	rid := rm.rid
	rm.rr[rid] = response
	rm.rmu.Unlock()

	//fmt.Println("send room rid:", rid)
	//debug.PrintStack()
	response.num = count
	startNow := time.Now()
	response.time = rm.timer.AfterFunc(rm.timeout, func() {
		success := false
		// 支持跨域通信，响应节点数至少为1
		if count == -1 && response.resNum >= 1 {
			success = true
		} else {
			fmt.Println("timeout", rm.id.ID(), rm.timeout, rm.rid, time.Now().Sub(startNow), response.num)
		}
		rm.rmu.RLock()
		response := rm.rr[rid]
		rm.rmu.RUnlock()
		if response != nil {
			response.Callback(success)
			response.finish()
		}
	})
	response.finish = func() {
		//fmt.Println("finish", rm.id.ID(), rm.timeout, rm.rid)
		rm.rmu.Lock()
		delete(rm.rr, rid)
		rm.rmu.Unlock()
		response.Release()
	}
	//fmt.Println(response)
	return rid
}

type response struct {
	num        int
	resNum     int
	callback   CallbackHandler
	callbackTo ToCallbackHandler
	result     map[string]interface{}
	time       timer.TimeNoder
	finish     func()
	mu         sync.Mutex

	getSerializer SerializerHandler
	isData        bool
}

var (
	responsePool = sync.Pool{}
)

func init() {
	responsePool.New = func() interface{} {
		r := &response{}
		r.init()
		return r
	}
}

func newResponse(callback CallbackHandler, callbackTo ToCallbackHandler) *response {
	if callback == nil && callbackTo == nil {
		return nil
	}
	res := responsePool.Get().(*response)
	res.callback = callback
	res.callbackTo = callbackTo
	res.num = 0
	res.resNum = 0
	res.getSerializer = nil
	res.isData = false
	return res
}

func (res *response) init() {
	res.result = make(map[string]interface{})
}

func (res *response) Release() {
	res.callbackTo = nil
	res.callback = nil
	res.init()
	if res.time != nil {
		res.time.Stop()
		res.time = nil
	}
	responsePool.Put(res)
}

func (res *response) Response(from string, data interface{}) {
	//fmt.Println("response", from, data)
	res.mu.Lock()
	res.resNum++
	res.result[from] = data
	res.mu.Unlock()
	res.CallbackTo(data)
	if res.resNum == res.num {
		if res.time != nil {
			res.time.Stop()
			res.time = nil
		}
		res.Callback(true)
		res.finish()
	}
}

func (res *response) Callback(success bool) {
	if res.time != nil {
		res.time.Stop()
		res.time = nil
	}
	if res.callback != nil {
		//fmt.Println("callback", success)
		res.callback(success, res.result)
	} else if !success && res.callbackTo != nil {
		//fmt.Println("callbackTo", success)
		res.callbackTo(success, nil)
	}
}

func (res *response) CallbackTo(data interface{}) {
	if res.callbackTo != nil {
		res.callbackTo(true, data)
	}
}
